﻿// FAIR WARNING
//
// Do not attempt to understand this algorithm because I don't.
// Well ok i'm just too lazy to comment.

using System;
using System.Collections.Generic;
using BoatNGoat.Logic.Map;

namespace BoatNGoat.Logic
{
    struct TerritorySize
    {
        public int Width;
        public int Height;
    }

    class SpawnPlacer
    {
        /// <summary>
        /// The herd territory is the size of box that only 1 herd of mobs will spawn in.
        /// </summary>
        public TerritorySize HerdTerritorySize { get; set; }

        /// <summary>
        /// The maximum number of mobs in a herd.
        /// </summary>
        public int MaxHerdSize { get; set; }

        /// <summary>
        /// The radius around each spawn point required to be land.
        /// E.g. 0.5 means a spawn point can be the centre of a tile.
        /// </summary>
        public double MinimumLandAroundSpawnPoint { get; set; }

        /// <summary>
        /// GET SOME SPAAAAAAAWNS
        /// </summary>
        /// <param name="map"></param>
        /// <returns></returns>
        public IList<SpawnPoint> CreateSpawnPoints(IMap map)
        {
            int across = map.Width/HerdTerritorySize.Width;
            int up = map.Height/HerdTerritorySize.Height;

            IList<SpawnPoint> spawns = new List<SpawnPoint>();

            for (int x = 0; x < across; ++x)
            {
                for (int y = 0; y < up; ++y)
                {
                    TileRange section = new TileRange();
                    section.Left = x * HerdTerritorySize.Width;
                    section.Bottom = y * HerdTerritorySize.Height;
                    section.Top = section.Bottom + HerdTerritorySize.Height;
                    section.Right = section.Left + HerdTerritorySize.Width;

                    CreateHerdSpawns(ref spawns, map, section, 0, MaxHerdSize);
                }
            }

            return spawns;
        }

        public SpawnPoint CreateSingleSpawn(IMap map)
        {
            TileRange wholeMap = new TileRange {Left = 0, Right = map.Width, Top = map.Height, Bottom = 0};

            IList<SpawnPoint> spawns = new List<SpawnPoint>(); 
            CreateHerdSpawns(ref spawns, map, wholeMap, 1, 1);

            if (spawns.Count != 1) throw new Exception("Whoops I did it again");

            return spawns[0];
        }

        private bool[,] MakeSection(IMap map, TileRange section)
        {
            bool[,] tiles = new bool[section.Width, section.Height];
            for (int x = 0; x < section.Width; ++x)
            {
                for (int y = 0; y < section.Height; ++y)
                {
                    Tile mapTile = map.Tiles[section.Left + x, section.Bottom + y];
                    tiles[x, y] = (mapTile == Tile.Forest || mapTile == Tile.Sand);
                }
            }
            return tiles;
        }

        private void CreateHerdSpawns(ref IList<SpawnPoint> list, IMap map, TileRange section, int MinInHerd, int MaxInHerd)
        {
            bool[,] tiles = MakeSection(map, section);
            bool[,] offsetTiles = OffsetSpawnableAreaIn(tiles, TilesToOffset());

            int landArea = LandArea(ref offsetTiles);
            int numberOfGoats = Rnd.Next(0, MaxHerdSize);

            while (landArea > 0 && numberOfGoats > 0)
            {
                int randomSpawnPoint = Rnd.Next(0, landArea);
                int x, y;

                NthSpawnableTile(ref offsetTiles, randomSpawnPoint, out x, out y);

                list.Add(
                    new SpawnPoint { 
                        MapPosition = new MapPosition(
                            section.Left + x + 0.5f,
                            section.Bottom + y + 0.5f)
                        }
                    );

                RemoveSquareAround(ref offsetTiles, x, y, TilesToOffset());

                --numberOfGoats;
                landArea = LandArea(ref offsetTiles);
            }
        }

        private void NthSpawnableTile(ref bool[,] tiles, int n, out int xResult, out int yResult)
        {
            int count = 0;
            for (int x = 0; x < tiles.GetLength(0); ++x)
            {
                for (int y = 0; y < tiles.GetLength(1); ++y)
                {
                    if (tiles[x, y])
                    {
                        if (count == n)
                        {
                            xResult = x;
                            yResult = y;
                            return;
                        }
                        ++count;
                    }
                }
            }
          
            throw new Exception("Loop shouldn't have finished.");
        }

        private void RemoveSquareAround(ref bool[,] tiles, int xCentre, int yCentre, int extraTilesAround)
        {
            int height = tiles.GetLength(1);
            int width = tiles.GetLength(0);

            int startX = Math.Max(xCentre - extraTilesAround, 0);
            int endX = Math.Min(xCentre + extraTilesAround, width - 1);
            int startY = Math.Max(yCentre - extraTilesAround, 0);
            int endY = Math.Min(yCentre + extraTilesAround, height - 1);

            for (int x = startX; x <= endX; ++x)
            {
                for (int y = startY; y <= endY; ++y)
                {
                    tiles[x, y] = false;
                }
            }

        }

        private int LandArea(ref bool[,] tiles)
        {
            int count = 0;
            foreach (bool b in tiles)
            {
                if (b) ++count;
            }

            return count;
        }



        private bool[,] OffsetSpawnableAreaIn(bool[,] tiles, int numTiles)
        {
            if (numTiles < 0) throw new ArgumentOutOfRangeException("numTiles");

            int height = tiles.GetLength(1);
            int width = tiles.GetLength(0);

            bool[,] offsetted = (bool[,])tiles.Clone();

            if (numTiles == 0)
            {
                return offsetted;
            }

            for (int x = 0; x < width; ++x)
            {
                for (int y = 0; y < height; ++y)
                {
                    bool left = false;
                    bool down = false;
                    bool right = false;
                    bool up = false;
                    // Have to assume that anything outside this tile region is blocked.
                    if (x > 0 && x < width - 1)
                    {
                        left = tiles[x - 1, y];
                        right = tiles[x + 1, y];
                    }
                    if (y > 0 && y < height - 1)
                    {
                        down = tiles[x, y - 1];
                        up = tiles[x, y + 1];
                    }

                    offsetted[x, y] = up && down && left && right;
                
                }
            }

            return OffsetSpawnableAreaIn(offsetted, numTiles - 1);
        }

        private int TilesToOffset()
        {
            return (int)Math.Ceiling(MinimumLandAroundSpawnPoint - 0.5);
        }

    }

    internal struct TileRange
    {
        public int Left { get; set; }
        public int Right { get; set; }
        public int Top { get; set; }
        public int Bottom { get; set; }
        public int Width { get { return Right - Left; } }
        public int Height { get { return Top - Bottom; } }
    }

}
